<style lang="scss">
.ua-audio {
  display: inline-block;
  position: relative;
  box-sizing: border-box;
  overflow: hidden;
  line-height: 1;
  &-border::before {
    content: '';
    display: inline-block;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    border: 1px dashed #e2e2e2;
    box-sizing: border-box;
  }
  input[type='file'] {
    display: inline-block;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    opacity: 0;
    cursor: pointer;
  }
  &--poster {
    width: 100%;
    height: 100%;
    overflow: hidden;
    z-index: 0;
    position: relative;
  }
  &--progress {
    width: 100%;
    height: 0;
    position: absolute;
    left: 0;
    bottom: 0;
    opacity: 0;
    transition: height 0.5s ease 0.2s, opacity 0.3s ease 0.3s;
    &-thumb {
      position: absolute;
      top: 0;
      left: -100%;
      width: 100%;
      height: 100%;
    }
  }
  &:hover &--progress {
    height: 7px;
    opacity: 0.6;
    &:hover {
      height: 15px;
      opacity: 1;
      cursor: pointer;
    }
  }
  &--tool {
    z-index: 2;
    cursor: pointer;
    @include translate-center(both);
  }
}
</style>

<template>
  <div class="ua-audio" :class="{ 'ua-audio-border': border }" :style="boxStyle">
    <audio
      ref="audioEl"
      :src="source"
      @timeupdate="onTimeupdate"
      @ended="onEnded"
      @canplay="onCanplay"
      @error="onError"
    />
    <div class="ua-audio--poster" v-if="!noPoster">
      <UImage :src="poster" :width="width" :height="height">
        <template #error>
          <UIconfont name="image-error" :size="imgErrorSize" color="#f5f7fa" />
        </template>
      </UImage>
    </div>
    <div
      class="ua-audio--progress"
      ref="progressEl"
      :style="{ backgroundColor: canplay ? '#f2f2f2' : '' }"
      @mousedown="onMousedown"
    >
      <div
        class="ua-audio--progress-thumb"
        :style="{
          transform: `translate3d(${played}%, 0, 0)`,
          backgroundColor: playIconColor,
        }"
      />
    </div>
    <div class="ua-audio--tool">
      <UIconfont
        :size="playIconSize"
        :name="canplay ? (status ? 'stop' : 'play') : 'music'"
        :cursor="canplay ? 'pointer' : 'not-allowed'"
        :color="canplay ? playIconColor : '#f5f7fa'"
        @click="status = !status"
      />
    </div>
  </div>
</template>

<script lang="ts">
import { qiniuFullLink } from '@assets/utils/qiniu'
import { fileRead } from '@assets/utils/util'
import { computed, defineComponent, Ref, ref, watch } from 'vue'
import UImage from './UImage.vue'
import UIconfont from './UIconfont.vue'

export default defineComponent({
  name: 'UAudio',
  components: { UImage, UIconfont },
  props: {
    src: { type: [String, File], default: '' },
    poster: { type: [String, File], default: '' },
    noPoster: { type: Boolean, default: false },
    border: { type: Boolean, default: true },
    width: {
      type: Number,
      default: 50,
    },
    height: {
      type: Number,
      default: 50,
    },
    playIconSize: {
      type: Number,
      default: 30,
    },
    playIconColor: {
      type: String,
      default: '#009688',
    },
  },
  setup(props) {
    let mousedown = false
    const unit = import.meta.env.VITE_LENGTH_UNIT
    // 音频资源
    const source = ref('')
    // 播放状态
    const status = ref(false)
    // 可以进行播放了
    const canplay = ref(false)
    // 播放进度
    const played = ref<number | string>(0)
    // <audio>
    const audioEl = ref<any>(null) as Ref<HTMLMediaElement>
    const progressEl = ref<any>(null) as Ref<HTMLDivElement>

    const boxStyle = computed(() => ({
      width: `${props.width}${unit}`,
      height: `${props.height}${unit}`,
    }))

    const imgErrorSize = computed(
      () => Math.min(props.width ?? 0, props.height ?? 0) * 0.4
    )

    watch(
      () => props.src,
      (nv) => {
        if (nv instanceof File) {
          fileRead(nv, 'base64').then((base64) => {
            source.value = base64
          })
        } else if (nv.length) {
          source.value = qiniuFullLink(nv)
        }
      },
      { immediate: true }
    )

    watch(status, (nv) => {
      if (source.value) {
        nv ? audioEl.value.play() : audioEl.value.pause()
      } else {
        status.value = false
      }
    })

    // audio 播放时间更新
    const onTimeupdate = (e: Event) => {
      if (mousedown) return
      const target = e.target as HTMLMediaElement
      if (target.duration && target.currentTime) {
        const val = target.currentTime / target.duration
        played.value = isNaN(val) ? 0 : (val * 100).toFixed(2)
      } else {
        played.value = 0
      }
    }

    // audio 播放结束
    const onEnded = () => {
      status.value = false
      played.value = 0
    }

    const onError = () => {
      status.value = false
      played.value = 0
      canplay.value = false
    }

    const onCanplay = () => {
      canplay.value = true
    }

    function setPlayed(x: number, l: number, w: number) {
      played.value = (((x - l) / w) * 100).toFixed(2)
    }

    const onMousedown = (e: MouseEvent) => {
      mousedown = true
      const { left, width } = progressEl.value.getBoundingClientRect()

      setPlayed(e.clientX, left, width)

      const mousemove = (ev: MouseEvent) => {
        setPlayed(ev.clientX, left, width)
      }

      const mouseup = () => {
        mousedown = false
        const currentTime = (Number(played.value) / 100) * audioEl.value.duration
        if (!isNaN(currentTime)) {
          audioEl.value.currentTime = currentTime
        }
        document.removeEventListener('mousemove', mousemove)
        document.removeEventListener('mouseup', mouseup)
      }

      document.addEventListener('mousemove', mousemove)
      document.addEventListener('mouseup', mouseup)
    }

    return {
      source,
      status,
      canplay,
      played,
      imgErrorSize,
      audioEl,
      progressEl,
      boxStyle,
      onTimeupdate,
      onEnded,
      onError,
      onCanplay,
      onMousedown,
    }
  },
})
</script>
